<script setup lang="ts">
import CpNavBar from '@/components/CpNavBar.vue'
import RoomStatus from './RoomStatus.vue'
import RoomAction from './RoomAction.vue'
import RoomMessage from './RoomMessage.vue'

import { io, type Socket } from 'socket.io-client'
import { baseURL } from '@/utils/request'
import { nextTick, onMounted, onUnmounted, provide, ref } from 'vue'
import { useUserStore } from '@/stores'
import { useRoute } from 'vue-router'
import type { Message, TimeMessages } from '@/types/room'
import { MsgType, OrderType } from '@/enums'
import { getConsultOrderDetail } from '@/services/consult'
import type { ConsultOrderItem, Image } from '@/types/consult'
import { showToast } from 'vant'

// 消息列表(包括历史消息和实时消息,前者是初次连接时服务器推送过来的,或者手动传触发查询历史记录,后者是实时聊天不断添加的)
const list = ref<Message[]>([])

const route = useRoute()
const store = useUserStore()
let socket: Socket

// 获取订单信息
const consult = ref<ConsultOrderItem>()
const loadConsult = async () => {
  const res = await getConsultOrderDetail(route.query.orderId as string)
  // res.data.status = 3 // 测试数据，强制变为咨询中,方便测试发送消息
  consult.value = res.data
}

// 发送消息给医生端
const sendText = (text: string) => {
  // 需要的参数是后端sendChatMsg接口定义的，这里调用的就是后端的接口方法sendChatMsg
  socket.emit('sendChatMsg', {
    from: store.user?.id, // 发送人
    to: consult.value?.docInfo?.id, // 收消息人
    msgType: MsgType.MsgText, // 消息类型
    msg: { content: text }, // 消息内容
  })
}
// 发送图片
const sendImage = (data: Image) => {
  console.log(data)
  socket.emit('sendChatMsg', {
    from: store.user?.id, // 发送人
    to: consult.value?.docInfo?.id, // 收消息人
    msgType: MsgType.MsgImage, // 消息类型
    msg: { picture: data }, // 消息内容
  })
}

// 下拉刷新
const loading = ref(false)
const msgtime = ref('')
const onRefresh = async () => {
  loading.value = true
  socket.emit('getChatMsgList', 10, msgtime.value, route.query.orderId) // 获取历史消息,每次10条,时间点,订单id
  loading.value = false
}

// 是否初次加载
const initialMsg = ref(true)

// 提供订单状态给子组件
provide('consult', consult)

// 评价成功后修改评价状态
const completeEva = (score: number) => {
  const item = list.value.find((item) => item.msgType === MsgType.CardEvaForm) // 一次对话只有一条评价信息，所以可以直接find
  if (item) {
    item.msg.evaluateDoc = { score }
    item.msgType = MsgType.CardEva
  }
}
provide('completeEva', completeEva) //知识点【组件通讯】：方法也可以通过inject/provide传递，记忆一下

onMounted(async () => {
  // 初始加载订单信息
  loadConsult()

  // 知识点【websocket的使用】：本项目演示socket.io-client的使用（client表示我们只需要实现客户端对接）
  // 1.连接服务器 （url就是本项目的地址，这里可以不用ws协议，因为socket.io会自动转换协议）
  socket = io(baseURL, {
    auth: { token: 'Bearer ' + store.user?.token },
    query: {
      orderId: route.query.orderId, //支付成功后支付页面跳转回本系统会携带订单id
    },
  })
  // 2.监听服务器连接成功
  socket.on('connect', () => {
    // 建立连接成功
    console.log('连接成功')
  })
  // 3.监听服务器发送的消息
  socket.on('error', (event) => {
    // 错误异常消息
    console.log('error', event)
  })
  // 4.监听服务器断开连接
  socket.on('disconnect', () => {
    // 已经断开连接
    console.log('Socket 连接已断开')
  })

  // 【知识点 经验】: 监听初次连接完成后，服务器一般都会自动推送一次历史消息
  // 这里监听的chatMsgList是后端约定好的事件名称，具体名称需要和后端协商或者看接口文档。向后端发起查询历史记录请求，需要通过它接收返回值
  socket.on('chatMsgList', (res) => {
    // console.log(res)
    const data: TimeMessages[] = res.data

    // 转换常规消息列表 (历史消息的createTime也需要展示为一条单独的消息,后端返回的数据没有把它放到items里,所以这里自行组装一下)
    const arr: Message[] = []
    data.forEach((item, i) => {
      if (i === 0) msgtime.value = item.createTime // 只取第一组的时间作为下一次查询(下拉刷新)的时间点（因为第一组是当前最早的时间）
      arr.push({
        msgType: MsgType.Notify,
        msg: { content: item.createTime },
        createTime: item.createTime,
        id: item.createTime,
      })
      arr.push(...item.items)
    })
    // 追加到聊天消息列表
    list.value.unshift(...arr)

    if (data.length === 0) showToast('没有更多历史消息了')

    // 如果是初次打开对话框,需要滚动到底部
    nextTick(() => {
      if (initialMsg.value) {
        // 初次加载标记当前所有消息为已读,参数是最新的消息id即可
        socket.emit('updateMsgStatus', arr[arr.length - 1].id)
        window.scrollTo(0, document.body.scrollHeight)
        initialMsg.value = false
      }
    })
  })

  // 监听订单状态的变化（本质就是重新获取订单的值） 在onMounted注册 （医生端的医生点击接单后，就会触发这个方法）
  socket.on('statusChange', () => {
    loadConsult()
  })

  // 接收消息(自己的和对方的都能收到)，msg就是消息体
  socket.on('receiveChatMsg', async (msg: Message) => {
    // console.log(msg)

    socket.emit('updateMsgStatus', msg.id) // 即时收到消息后，标记为已读

    list.value.push(msg) // 追加到聊天消息列表
    //知识点：nextTIck的使用，数据更新后触发视图更新，但是这个更新是在当前同步代码执行完后进行，所以这里必须加上await nextTick()让下面的代码
    //在视图更新完毕后再执行
    // 这里需要等dom更新完毕后再执行window.scrollTo,否则会获取到旧的高度，无法滚动
    await nextTick()
    window.scrollTo(0, document.body.scrollHeight)
  })
})

// 组件卸载时（关闭页面），断开连接
onUnmounted(() => {
  // 5.断开连接
  if (socket) {
    socket.disconnect() // 第4步会监听到这个方法调用
  }
})
</script>
<template>
  <div class="room-page">
    <cp-nav-bar title="问诊室"></cp-nav-bar>
    <room-status :status="consult?.status" :countdown="consult?.countdown"></room-status>
    <van-pull-refresh v-model="loading" @refresh="onRefresh">
      <room-message v-for="item in list" :item="item" :key="item.id"></room-message>
    </van-pull-refresh>
    <!-- 不等于咨询中都变为不可操作 -->
    <room-action
      @send-image="sendImage"
      @send-text="sendText"
      :disabled="consult?.status !== OrderType.ConsultChat"
    ></room-action>
  </div>
</template>

<style scoped>
.room-page {
  padding-top: 90px;
  padding-bottom: 60px;
  min-height: 100vh;
  box-sizing: border-box;
  background-color: var(--cp-bg);
  .van-pull-refresh {
    width: 100%;
    min-height: calc(100vh - 150px);
  }
}
</style>
